/*  GStruct: data structure helpers for Java
 *  Copyright (C) 2011 Giulio Franco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package gstruct.sequential;

import gstruct.extensions.Predicates;
import gstruct.extensions.Sequential;

import java.util.AbstractList;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;

class RigidList<E> extends AbstractList<E> {
	
	private E[] content;
	int size = 0;
	private final ArrayDeque<Integer> freeBlocks;
	
	public RigidList() {
		this(0);
	}
	
	@SuppressWarnings("unchecked")
	public RigidList(int capacity) {
		if (capacity < 0) {
			throw new IllegalArgumentException("Capacity cannot be less than zero. " + capacity + " given.");
		}
		content = (E[]) new Object[capacity];
		freeBlocks = new ArrayDeque<>(capacity);
		for (int i = 0; i < capacity; ++i) {
			freeBlocks.offer(i);
		}
	}
	
	public RigidList(E[] data) {
		content = Arrays.copyOf(data, data.length);
		freeBlocks = new ArrayDeque<>(data.length);
		for (int i = 0; i < data.length; ++i) {
			if (content[i] == null) {
				freeBlocks.offer(i);
			} else {
				size += 1;
			}
		}
	}
	
	public RigidList(Collection<? extends E> data) {
		this(data.size());
		for (E e : data) {
			add(e);
		}
	}
	
	@Override
	public E get(int arg0) {
		try {
			return content[arg0];
		} catch (ArrayIndexOutOfBoundsException ex) {
			throw new IndexOutOfBoundsException(arg0 + "/" + content.length);
		}
	}

	@Override
	public int size() {
		return content.length;
	}
	
	public int usedSize() {
		return size;
	}

	@Override
	public boolean add(E e) {
		try {
			content[freeBlocks.remove()] = e;
		} catch (NoSuchElementException ex) {
			throw new UnsupportedOperationException("The list is full");
		}
		return true;
	}

	@Override
	public void add(int index, E element) {
		if (content[index] == null) {
			content[index] = element;
			freeBlocks.remove(index);
		} else {
			throw new UnsupportedOperationException("Can't add to an occupied position");
		}
	}

	@Override
	public void clear() {
		for (int i = 0; i < content.length; ++i) {
			if (content[i] != null) {
				content[i] = null;
				freeBlocks.offer(i);
			}
		}
	}

	@Override
	public Iterator<E> iterator() {
		return Sequential.filter(Arrays.asList(content), Predicates.nonnullPredicate()).iterator();
	}

	@Override
	public ListIterator<E> listIterator() {
		return Sequential.filterNow(this, Predicates.nonnullPredicate()).listIterator();
	}

	@Override
	public ListIterator<E> listIterator(int index) {
		return Sequential.filterNow(this, Predicates.nonnullPredicate()).listIterator(index);
	}

	@Override
	public E remove(int index) {
		E res = content[index];
		if (res != null) {
			content[index] = null;
			freeBlocks.offer(index);
		}
		return res;
	}

	@Override
	public E set(int index, E element) {
		E res = content[index];
		if (res == null && element != null) {
			freeBlocks.remove(index);
		} else if (res != null && element == null) {
			freeBlocks.offer(index);
		}
		content[index] = element;
		
		return res;
	}

	@Override
	public boolean isEmpty() {
		return usedSize() == 0;
	}
}
